/**
 * 
 */
package coreAlgorithm;

import java.util.Arrays;
import java.util.Comparator;
import java.util.Iterator;

import exceptions.KBucketUpdateException;
import exceptions.NodeNotFoundException;

/**
 * @author peter
 * 
 */
public class KBucket implements Iterable<NodeTriple> {
	public static final int K = 20;
	public NodeTriple[] kBucket = new NodeTriple[K];
	private final Node node;



	public KBucket(Node node) {
		super();
		this.node = node;
	}

	/* 
	 * Erzeugt einen LEAST_RECENTLY_SEEN_FIRST_COMPARATOR
	 */
	@Override
	public Iterator<NodeTriple> iterator() {
		return iterator(NodeTriple.LEAST_RECENTLY_SEEN_FIRST_COMPARATOR);
	}

	public Iterator<NodeTriple> iterator(Comparator<NodeTriple> comp) {
		return new KBucketIterator(kBucket, comp);
	}

	public void addOrUpdateUnknownNodeTriple(NodeTriple nodeTriple) {
		if(nodeTriple.getNodeID().equals(node.getId())) {
			throw new KBucketUpdateException("Habe versucht, mein Bucket mit mir selbst upzudaten!");
		}
		int emptyBucket = -1;
		for (int i = 0; i < kBucket.length; i++) {
			if (kBucket[i] == null) {
				emptyBucket = i;
				continue;
			}
			if (nodeTriple.getNodeID().equals(kBucket[i].getNodeID())) {
				// update NodeTriple
				updateKnownNodeTriple(kBucket[i]);
				return;
			}
		}
		// if NodeTriple is new, try to insert it
		if (emptyBucket > -1) {
			kBucket[emptyBucket] = nodeTriple;
			kBucket[emptyBucket].setLastSeen(node.getClock().getTime());
			
		} else {
			// if kBucket is full, ping least-recently seen node
			NodeTriple leastRecentlySeen = this.iterator().next();
			if (node.sendPing(leastRecentlySeen.getAddress())) {
				// kBucket is ok
				// update the least-recently to the most-recently
				updateKnownNodeTriple(leastRecentlySeen);
				// Discard the new node
			} else {
				emptyBucket = removeNodeTriple(leastRecentlySeen);
				kBucket[emptyBucket] = nodeTriple.clone();
				kBucket[emptyBucket].setLastSeen(node.getClock().getTime());
			}
		}
	}

	public void updateKnownNodeTriple(NodeTriple nodeTriple) {
		getNodeTriple(nodeTriple.getNodeID()).setLastSeen(node.getClock().getTime());
	}

	public int removeNodeTriple(NodeTriple nt) {
		for (int i = 0; i < kBucket.length; i++) {
			if (kBucket[i] == null)
				continue;
			if (kBucket[i].getNodeID().equals(nt.getNodeID())) {
				kBucket[i] = null;
				return i;
			}
		}
		throw new IllegalArgumentException("NodeTriple wasn't found in this bucket");
	}

	/**
	 * Copies the list at initialization, to make itself robust against changes
	 * of the aggregate.
	 * 
	 * The most-recently seen Node will be at tail of the list, i.e. the highest
	 * index The least-recently seen Node will be at the head of the list, i.e.
	 * the lowest index
	 * 
	 * @author peter
	 * 
	 */
	private class KBucketIterator implements Iterator<NodeTriple> {
		private int x = 0;
		private NodeTriple[] myAggregate;
		private Comparator<NodeTriple> myComparator;

		public KBucketIterator(NodeTriple[] aggregate, Comparator<NodeTriple> comp) {
			super();
			myAggregate = new NodeTriple[aggregate.length];
			myComparator = comp;

			for (int i = 0; i < aggregate.length; i++) {
				if (aggregate[i] == null) {
					myAggregate[i] = null;
				} else {
					myAggregate[i] = aggregate[i].clone();
				}
			}

			sort();
		}

		@Override
		public boolean hasNext() {
			return (x < myAggregate.length && myAggregate[x] != null);
		}

		@Override
		public NodeTriple next() {
			return myAggregate[x++];
		}

		@Override
		public void remove() {
			throw new UnsupportedOperationException();
		}

		private void sort() {
			Arrays.sort(myAggregate, myComparator);
		}

		public String toString() {
			String result = "";
			for (int i = 0; i < myAggregate.length; i++) {
				if (myAggregate[i] == null) {
					result += "null\n";
				} else {
					result += myAggregate[i].toString() + "\n";
				}
			}
			result += "\n\n";
			return result;
		}

	}

	public NodeTriple getNodeTriple(ID demandedNode) {
		for (int i = 0; i < kBucket.length; i++) {
			if (kBucket[i] == null)
				continue;
			if (demandedNode.equals(kBucket[i].getNodeID())) {
				return kBucket[i];
			}
		}
		throw new NodeNotFoundException("The requested node is not in this bucket!");
	}

	public void print() {
		Iterator<NodeTriple> iter = iterator();
		while (iter.hasNext()) {
			NodeTriple nodeTriple = (NodeTriple) iter.next();
			System.out.print(nodeTriple.getNodeID());
			if(iter.hasNext()) System.out.print(", ");
		}
		
	}

}
